Newsletter: flip the modernization filter default + retire legacy (PR 6 / #48530)#48613
Newsletter: flip the modernization filter default + retire legacy (PR 6 / #48530)#48613keoshi wants to merge 25 commits into
Conversation
Surface the value of `jetpack_wp_admin_subscriber_management_enabled` as `subscriberManagementEnabled` in the Newsletter script data so the modernized dashboard can hide the Subscribers tab on hosts that defer subscriber management to Calypso, instead of inferring intent from the URL we ship under `subscriberManagementUrl`. Pure metadata — the filter remains the source of truth and the existing URL shape is unchanged.
Introduce `NewsletterPage` as the shared shell for the modernized dashboard: a single `Page` from `@wordpress/admin-ui` wraps a persistent `Tabs.Root` from `@wordpress/ui` driven by a `?tab=` URL param so the animated active-tab indicator slides between Subscribers and Settings instead of remounting on each route hop. Switching tabs also strips the subscriber-detail params (`subscriber`, `u`) so the inspector doesn't hitchhike across to Settings. The chrome polishes the page surface to match Forms: an off-white background so cards stand out, a sticky page header without a bottom rule, a sticky tab row pinned by a ResizeObserver-tracked CSS variable, and a flex chain that lets DataViews fill the viewport on Subscribers while letting Settings keep its content-tall scrolling model. Hides the Subscribers tab when `jetpack_wp_admin_subscriber_management_enabled` is filtered to false, so hosts that defer subscriber management to Calypso land on a Settings-only page.
Move the 9 settings sections, their state hooks, and the save handlers into a new `NewsletterSettingsBody` component under `src/settings/newsletter-settings.tsx`. The legacy `NewsletterSettingsApp` becomes a thin chrome wrapper that mounts the body inside `AdminPage` / `Container` / `Col` and runs the WP.com connection check before passing `hasConnectedOwner` and `connectUrl` in as props. The 9 sections themselves are unchanged. The split lets the modernized dashboard mount the body inside its `Tabs.Panel` without dragging `@automattic/jetpack-components` and `@automattic/jetpack-connection` (and their SCSS chains) through the wp-build pipeline. Snackbars dispatch straight to `@wordpress/notices`'s `noticesStore` — the legacy chrome keeps its `<GlobalNotices />` for surfacing, the wp-build polyfills' `SnackbarNotices` already covers the modernized side. Also switch `newsletter-section.tsx` to import `getRedirectUrl` via the package's `./tools/jp-redirect` subpath, so dropping a section into the wp-build bundle doesn't drag the rest of the components package in with it.
Add a `./components/wpcom-support-link` export so consumers that only need the support-link helper can import it without dragging the rest of the components barrel — and crucially the SCSS-laden `Nudge` — through their bundler. The Newsletter modernized dashboard switches its categories section to the new subpath so the wp-build pipeline can resolve the section without pulling in upgrade-nudge.
… its panel Replace the inline `Page` chrome with `NewsletterPage` and feed both tabs from the same `Tabs.Root`. `?tab=settings` flips to the Settings panel, where `NewsletterSettingsBody` renders the existing 9-section form unchanged on its legacy primitives — PR 5 will move those onto WPDS `Card`. Subscribers stays the default and inherits the row-action and header-action behaviour shipped in PR 3. The route module gains `@automattic/jetpack-api`, `@automattic/jetpack-shared-extension-utils`, and `@wordpress/url` so the settings body and its sections — which import from those packages — resolve once they're mounted inside the wp-build bundle. The inactive panel renders `null` so we don't pay for the other view's data fetching until a visitor opens it.
`route.inspector` decides visibility from search params alone, so a deep-link like `?tab=settings&subscriber=123` would otherwise pop the detail panel open on top of the Settings page. Bail out early when the active tab is Settings; `NewsletterPage` already strips `subscriber` and `u` on tab change, but this guard covers manual URL edits too.
…ssis `@wordpress/route`'s `navigate` doesn't accept a search-updater function, so passing `search: ( prev ) => ( ... )` no-ops the search state — the new `?tab=` param never made it into the router and tabs never switched. Pass an object literal instead, then read the active tab from `search.tab` as before. When `jetpack_wp_admin_subscriber_management_enabled` is filtered to false the page is Settings-only, but the stage was still rendering two `Tabs.Panel`s under a chrome that no longer wrapped them in a `Tabs.Root`. That tripped the Base UI invariants (`TabsRootContext is missing`, then `Tab/Panel count mismatch` once we trimmed one Panel). Pin `activeTab` to `settings` when subscribers are hidden and render `<NewsletterSettingsBody />` directly, with no `Tabs.Panel` wrapper, so the Settings-only path never crosses the Tabs primitive at all.
…e stage `@wordpress/admin-ui`'s `Page` puts `overflow: auto` on the body wrapper itself, so the body wrapper — not the outer stage — is the scroll container. The page header is a sibling of the body wrapper, not a parent. That meant the previous `top: var(--header-height)` on `.jetpack-newsletter-page__tabs-row` was sticking the row 92px below the body-wrapper top, leaving an empty band where the dataviews chrome showed through. Switch the sticky offset to `top: 0` (the body wrapper already starts directly under the page header) and drop the `ResizeObserver` that fed `--jetpack-newsletter-header-height` — nothing reads it anymore. Admin-ui also sets `flex: 1 1 auto` on every direct child of the body wrapper, which was outweighing the tabs-row's `flex-shrink: 0` and collapsing it to ~24px so the 48px tab buttons clipped out of view. Bump the tabs-row selector to mirror admin-ui's `> :not(.admin-ui-page__header):not(.jetpack-footer) > *` path so `flex: 0 0 auto` actually wins, and the bar grows to its natural height with the active-tab indicator visible.
Subscribers ships header actions ("Add subscribers" + the More menu)
that pulled the header's title row to 36px, while Settings shipped no
actions and let the row collapse to its 28px text height — so swapping
tabs nudged the whole sticky row plus everything underneath by 8px.
Pin `.admin-ui-page__header > *:first-child` to a 36px min-height so
the title row matches the action-button height regardless of whether
actions are present. The 8px jump is gone, the tab bar no longer
re-positions on tab change.
Render `JetpackLogo` (mark only, no text) inline with "Newsletter" in the page-header title slot, matching the wp-build chassis convention that Forms, Backup, and VideoPress already use. The mark sits inside a `Stack` with an 8px gap so the brand and the product name read as one title unit, and the `ariaLabel` keeps the screen-reader name as plain "Newsletter". The logo arrives via the package's `./jetpack-logo` subpath so we don't drag the rest of `@automattic/jetpack-components` (and its SCSS chain) into the wp-build bundle.
The default header was leaving roughly 16px of slack between the subtitle and the tab nav directly underneath, which read as a stray band of empty page surface once the tab row was anchored to the body wrapper. Pin `padding-block-end` on `.admin-ui-page__header` to `--wpds-dimension-padding-xs` (4px) so the tab nav docks straight up against the subtitle.
Move all 9 settings sections from `@wordpress/components`'s `Card` / `CardHeader` / `CardBody` / `CardFooter` over to `Card.Root` / `Card.Header` / `Card.Title` / `Card.Content` from `@wordpress/ui`. `__experimentalHeading` (legacy heading inside the header) becomes `Card.Title`; `__experimentalText` becomes WPDS `Text`. Section internals — DataForm fields, save handlers, change tracking — stay byte-for-byte identical. WPDS `Card` doesn't ship a `Footer` slot, so the row that used to live inside `<CardFooter>` becomes a `<div className="newsletter-card-footer">` at the bottom of `<Card.Content>`. The shared style: * save-button rows (Subscriptions, Newsletter categories, Sender settings, Welcome email) right-align via `justify-content: flex-end`, * the Newsletter section's two-link row (Privacy + Manage subscribers) brackets via a `:has(> :nth-child(2))` rule that switches to `space-between` only when there's more than one child. Save and action buttons (`Save`, `Add plans` / `Manage plans`) opt into `__next40pxDefaultSize` so they ship the WordPress 7 default height ahead of the upgrade. Pure refactor — no behaviour changes.
…elow The settings body stacked its cards with `gap="md"` (12px), which left them sitting too close together on the modernized chassis once they got the new WPDS Card chrome. Switch the inner and outer `Stack`s to `gap="xl"` so each card pair sits exactly 24px apart via `--wpds-dimension-gap-xl` — same rhythm as the page-header padding inset, no magic numbers. Add a `padding-block-end` of `--wpds-dimension-padding-3xl` (32px) to `.newsletter-settings` so the last card has room before the page bottom on overflow scroll.
|
Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.
Interested in more tips and information?
|
|
Thank you for your PR! When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:
This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖 Follow this PR Review Process:
If you have questions about anything, reach out in #jetpack-developers for guidance! Jetpack plugin: The Jetpack plugin has different release cadences depending on the platform:
If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack. |
Bring the prototype's placement-card surface back: a selectable card composed from `<label htmlFor>` + `CheckboxControl`, a 16:9-ish SVG wireframe slot, a caption with an optional "Preview and edit" link, and a brand-toned active-stroke driven by `data-checked="true"`. Ported verbatim — same component API the prototype settled on so the SubscriptionsSection can swap its flat `ToggleWithEditorLink` rows for a 2×2 grid of cards in the next commit. The four illustration components (`OverlayIllustration`, `PopupIllustration`, `EndOfPostIllustration`, `FloatingIllustration`) ship as inline 294×192 SVG wireframes — pure vector, no external assets. Subgroup heading and `<fieldset>` reset styles join the package stylesheet alongside the placement-card scss for the neighbouring Navigation / Comments groups.
Replace the flat `ToggleWithEditorLink` rows in the Subscriptions card with a 2×2 grid of `PlacementCard`s — overlay, modal, end-of-post, floating button — each backed by its own SVG wireframe and an optional "Preview and edit" link to the matching site-editor template part. Navigation and Comments stay as inline `ToggleControl` subgroups inside the same card so the whole subscription configuration reads without scrolling between cards. Telemetry catches up with the prototype: * `jetpack_newsletter_placement_toggle` fires on every placement card flip, tagged with the placement slug + new value. * `jetpack_newsletter_placement_preview_click` fires when the user clicks a card's preview link. * `jetpack_newsletter_section_save` payload gains `changed_keys` / `change_count` so we can see what's actually in each user's batch instead of just the section name. The orchestrator will start passing the staged keys in the next commit. The section accepts an optional `changedKeys` prop (still optional for now so the orchestrator wiring lands separately without breaking the build) and falls back to an empty array if the parent doesn't supply it.
The legacy `NewsletterSection` doubled as a master `subscriptions`
toggle and a "send new posts as newsletters by default" toggle.
Now that the master toggle moves to the global Newsletter module
activation control, drop it from this UI and reshape the surviving
`wpcom_newsletter_send_default` field into a focused, email-themed
card called **Email defaults**.
While the section was being reshaped:
* Drop the "Privacy information" + "Manage all subscribers" footer
links — they don't fit the email-defaults framing, and the latter is
redundant with the Subscribers tab now living one click away.
* Rename the file to `sections/email-defaults-section.tsx` and the
exported component to `EmailDefaultsSection`. Update
`sections/index.ts` accordingly.
Reshuffle the IA in `newsletter-settings.tsx` to walk users from
activation flavour → distribution → email behaviour → categorisation:
Paid → Subscriptions → Email defaults → Email content → Email byline
→ Email sender settings → Email reply-to settings → Welcome email →
Newsletter categories.
Drop the always-editable wrapper around the deleted
`NewsletterSection`; everything now sits inside the same
`<Disabled isDisabled={ ! data.subscriptions }>` boundary that gates
the rest of the page on module status.
Subscriptions, Sender, Categories, and Welcome each fire a `jetpack_newsletter_section_save` Tracks event when the user clicks their Save button. Until now those events only carried the section name — useful for counts but not for understanding which fields users actually flip together inside a single batch. Each section now accepts an optional `changedKeys: string[]` prop and forwards it as `changed_keys` (comma-joined) + `change_count` on the save event. The orchestrator wires it from the changeset state it already maintains for each section's manual save (`subscriptionChanges`, `senderNameChanges`, `newsletterCategoriesChanges`, `welcomeEmailChanges` → `Object.keys()`).
Every Jetpack site now lands on the unified Subscribers/Settings chassis we've been building since PR 1. The legacy `AdminPage` chrome stays in the bundle behind the same filter, so hosts that need the old surface back can opt out with `add_filter( 'rsm_jetpack_ui_modernization_newsletter', '__return_false' );` until the legacy code paths are removed in a follow-up.
The unified Newsletter page now owns the Subscribers tab, so neither the wp-admin Subscribers submenu nor the "Subscribers ↗" Calypso link belongs in the sidebar anymore. * `modules/subscriptions.php`: drop the `add_subscribers_menu` method (and its `jetpack_admin_menu` hook) that registered the Calypso shortcut on self-hosted, plus the `Subscribers_Dashboard` init that registered the standalone wp-admin Subscribers page on hosts with `jetpack_wp_admin_subscriber_management_enabled` on. Drop the now-unused `Admin_Menu` and `Redirect` imports. * `jetpack-mu-wpcom/wpcom-admin-menu.php`: collapse the `// Jetpack > Subscribers.` if/else into a single `wpcom_hide_submenu_page` call — the Calypso link no longer surfaces here either way, but we keep the hide so anything else that adds it back stays out of the sidebar. Drop `Subscribers_Dashboard` import and remove `'subscribers'` from the Jetpack submenu order list. Newsletter is now the single Subscribers entry point. The `subscribers-dashboard` package will be deleted in the next commit.
Now that no plugin instantiates `Subscribers_Dashboard` (the previous commit retired both call sites), the package has no consumers and can go. Delete `projects/packages/subscribers-dashboard/` outright. Pull the `automattic/jetpack-subscribers-dashboard` dep out of `plugins/jetpack/composer.json`, `packages/jetpack-mu-wpcom/composer.json`, and `packages/masterbar/composer.json` (none of those packages were using it directly), refresh the Jetpack plugin's `composer.lock` and the workspace `pnpm-lock.yaml` so the autoload classmap and pnpm graph stop carrying the package. Standalone `Subscribers` page is fully retired — Newsletter is the single Subscribers entry now.
Record a tracks event whenever the visitor flips to a different tab —
`{ site_type, tab }`. The guard skips the no-op case where
`next === activeTab`, so refreshes and same-tab nav don't pollute the
counts. Analytics is already initialized at the page Stage (PR 2), so
the event reaches Tracks immediately on first interaction.
PR 2 lifted analytics initialization to the page Stage but `load_admin_scripts` was still short-circuiting before any script enqueue ran when modernization was on. `analytics.initialize` queues events into `window._tkq`, but without the Tracks transport (`stats.wp.com/w.js`) loaded, the queue grows forever and no `pixel.wp.com/t.gif` requests fire — telemetry from PR 6's new `jetpack_newsletter_tab_view` event went nowhere. Move the `jp-tracks` enqueue above the modernized short-circuit so it runs on both surfaces; everything else (the legacy `jetpack-newsletter` bundle, JetpackScriptData, etc.) stays gated to the legacy path.
7f749e1 to
cc6bb99
Compare
77b3ffe to
4e9e166
Compare
The Card-primitives migration shared `NewsletterSettingsBody` between the legacy `wp-admin/admin.php?page=jetpack-newsletter` route and the modernized chassis, but the IA reshape (master toggle removed, `EmailDefaultsSection` split out, placement-card grid, dropped privacy/manage links) had no flag wrapping it — so the legacy surface silently picked up every modernized behaviour even with `rsm_jetpack_ui_modernization_newsletter` off. That made the changelog claim "no user-visible change unless the flag is enabled" wrong on the legacy page. Add a defaulted-false `isModernized` prop to `NewsletterSettingsBody` and branch only the section composition on it. The shared state — API fetch, optimistic update, change tracking, snackbar dispatch — stays single-source. The chassis passes `isModernized` from `routes/dashboard/stage.tsx`; the legacy mount in `src/settings/index.tsx` doesn't change at all (default suffices). Legacy branch keeps trunk's exact composition: `NewsletterSection` (master `subscriptions` toggle + Privacy / Manage subscribers footer links) outside the disabled-by-subscriptions wrapper, then `LegacySubscriptionsSection` (flat `ToggleWithEditorLink` list grouped into Homepage / Navigation / Comments subgroups) inside it, followed by the trunk section order. Both legacy files retire together with the legacy chrome in #48613. Also fix `tests/settings.test.jsx`: the jest mock for `../src/settings/sections` lagged the renames in commit 6 (no `EmailDefaultsSection`), so the consumer rendered `undefined` and the test suite dropped from 8/8 to 4/8. The mock now matches the real exports, including `LegacySubscriptionsSection` and `NewsletterSection`. Suite is 8/8 again. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes #48530 — final PR of the six-PR Newsletter modernization rollout. Stacked on #48608 (Settings on WPDS Card primitives). Once #48608 merges, GitHub auto-flips this PR's base to trunk.
Proposed changes
true.Newsletter\Settings::is_modernized()now defaults thersm_jetpack_ui_modernization_newsletterfilter totrue. Every Jetpack site lands on the unified Subscribers/Settings chassis without any opt-in. Hosts that need the legacyAdminPagesurface back canadd_filter( 'rsm_jetpack_ui_modernization_newsletter', '__return_false' );. The Subscribers REST gate (class-wpcom-rest-api-v2-endpoint-subscribers-list.php) flips its default the same way so the proxy registers without an explicit filter.modules/subscriptions.phpdrops theadd_subscribers_menumethod (the "Subscribers ↗" Calypso link on self-hosted) and theSubscribers_Dashboard::init()call (the wp-admin Subscribers page registration).jetpack-mu-wpcom'swpcom-admin-menu.phpcollapses the// Jetpack > Subscribers.if/else into awpcom_hide_submenu_pagecall so nothing surfaces the Calypso link, and'subscribers'drops out of the Jetpack submenu order list. Newsletter is the single Subscribers entry now.subscribers-dashboardpackage. Deleteprojects/packages/subscribers-dashboard/outright and pullautomattic/jetpack-subscribers-dashboardfromplugins/jetpack,packages/jetpack-mu-wpcom, andpackages/masterbar. Refreshcomposer.lock(Jetpack plugin) andpnpm-lock.yamlso the autoload classmap and pnpm graph stop carrying the package.jetpack_newsletter_tab_viewTracks event. Fire it fromNewsletterPage'sonTabChangewith{ site_type, tab }whenever the visitor flips to a different tab; same-tab clicks no-op. Analytics init was already lifted to the page Stage in PR 2, but PR 2'sload_admin_scriptsshort-circuit was also skipping thejp-tracksenqueue, so events queued into_tkqforever andpixel.wp.com/t.gifnever fired. Move thejp-tracksenqueue above the modernization short-circuit so the Tracks transport loads on both surfaces.ARIA + RTL audited in the browser:
role="tablist"+role="tab"+role="tabpanel"with the rightaria-selected/aria-labelledby/tabindexplumbing out of the box (Base UI under the hood).dir="rtl"flips the page header (logo + "Newsletter" sit on the right, in the correct semantic order), the Subscribers/Settings tab nav, and the Newsletter card's two-link footer (Privacy / Manage subscribers bracket the row in flipped positions).Related product discussion/links
Does this pull request change what data or activity we track or use?
jetpack_newsletter_tab_viewfires with{ site_type, tab }on tab change. No new identifiers or PII; matches the pattern other Jetpack page-tab events use.add_script_datapayload. No new keys (thesubscriberManagementEnabledkey landed in PR 4).Testing instructions
Pull this branch, run
pnpm install,composer installinsideprojects/plugins/jetpack/, andpnpm jetpack build packages/newsletter --depsto refresh the Newsletter package and its dependencies.Default-on (the new behaviour)
add_filter( 'rsm_jetpack_ui_modernization_newsletter', ... )lives in any mu-plugin /wp-config.php/ theme / Code Snippet.wp-admin/admin.php?page=jetpack-newsletter. The unified chassis renders straight away — page header with the Jetpack mark + "Newsletter", Subscribers/Settings tabs, the WPDS Card layout in Settings, the Subscribers DataViews list under the Subscribers tab.jetpack_wp_admin_subscriber_management_enabledon).pixel.wp.com/t.gif?...&_en=jetpack_newsletter_tab_view&tab=...requests fire when switching tabs (DevTools → Network, filterpixel.wp.com).Opt-out path
add_filter( 'rsm_jetpack_ui_modernization_newsletter', '__return_false' );to a mu-plugin or Code Snippet.AdminPagechrome with the green logo header). The settings still save, the cards still render with WPDS primitives (PR 5 already updated the shared body).RTL smoke check
dir="rtl"on<html>/<body>in DevTools.Keyboard / ARIA smoke check
Screenshots
To be added once the PR review begins (the visual surface is the same as #48608 with the modernization filter implicitly on).